
#include "main.h"
#include "format.h"
#include "util.h"
#include "log.h"

int log_level = 0;
static int help_flag, version_flag;

static const struct option long_options[] = {
        {"help",    no_argument,       &help_flag,    1},
        {"version", no_argument,       &version_flag, 1},
        {"debug",   optional_argument, 0,             'd'},
        {"format",  required_argument, 0,             'f'},
        {"map",     required_argument, 0,             'm'},
        {"scatter", required_argument, 0,             's'},
        {0,         0,                 0,             0}
};


void context_init(struct context *ctx) {
    ctx->ro_size = 0;
    ctx->ro_used_size = 0;
    ctx->rw_size = 0;
    ctx->rw_used_size = 0;
    ctx->rom_size = 0;
    ctx->rom_used_size = 0;

    ctx->format = &format_percent;
}


int parse_cmdline_args(struct context *ctx, int argc, char *argv[]) {
    int c;

    while (1) {
        /* getopt_long stores the option index here. */
        int option_index = 0;

        c = getopt_long(argc, argv, "hvd::f:m:s:", long_options, &option_index);

        /* Detect the end of the options. */
        if (c == -1)
            break;

        switch (c) {
            case 0:
                break;
            case 'h':        /* --help    | -h  */
                help_flag = 1;
                break;
            case 'v':        /* --version | -v  */
                version_flag = 1;
                break;
            case 'd':        /* --debug   | -d  */
                log_level = strtol(optarg, NULL, 10);
                LOG_DEBUG("log level:%d\n", log_level);
                break;
            case 'f': {      /* --format  | -f  */
                LOG_INFO("format:%s\n", optarg);
                if (strcmp(optarg, "percent") == 0) {
                    ctx->format = &format_percent;
                } else if (strcmp(optarg, "progbar") == 0) {
                    ctx->format = &format_progress_bar;
                } else if (strcmp(optarg, "sector") == 0) {
                    ctx->format = &format_sector;
                } else {
                    LOG_WARNING("Unsupported format option:%s, use default 'percent'\n", optarg);
                }
                break;
            }
            case 'm': {      /* --map     | -m  */
                LOG_INFO("Keil uVision map file:%s\n", optarg);
                ctx->map_file = optarg;
                break;
            }
            case 's': {      /* --scatter | -s  */
                LOG_INFO("Keil uVision map file:%s\n", optarg);
                ctx->scatter_file = optarg;
                break;
            }
            default:        /* '?'              */
                LOG_WARNING("Unsupported option for %c\n", c);
                return -1;
        }
    }

    if (optind < argc) {
        /* Catch extra arguments on the command line. */
        printf("Unexpected command line argument: %s\n", argv[optind]);
        return -1;
    }

    if (help_flag) {
        printf("calcelf(Calculate Executable file)\nLicensed under GNU GPL v2\n");
        printf("\t--help       | -h\tdisplay this help\n");
        printf("\t--version    | -v\tdisplay program version\n");
        printf("\t--debug      | -d\tset debug level. available options:(-d3)\n");
        printf("\t--format     | -f\tset format to print. available options:(percent, progbar)\n");
        printf("\t--map        | -m\tset Keil5 uVision .map file <name>\n");
        printf("\t--scatter    | -s\tset Keil5 uVision .sct file <name>\n");
        exit(-1);
    }

    if (version_flag) {
        printf("%s\n", MEM_REGION_VERSION);
        exit(0);
    }

    return 0;
}

int parse_map_file(struct context *ctx) {
    FILE *fp;
    fp = fopen(ctx->map_file, "r");
    if (fp == NULL) {
        LOG_ERROR("Failed to open file %s\n", ctx->map_file);
        return ERROR_FILE_NOT_FOUND;
    }

    if (fseek(fp, MAP_FILE_OFFSET, SEEK_END) != 0) {
        LOG_ERROR("Failed to seek to target location %d\n", MAP_FILE_OFFSET);
        fclose(fp);
        return ERROR_SEEK_FAILED;
    }

    char *end;
    errno = 0;
    char line[120];
    char *result[15];
    while (fgets(line, sizeof(line), fp) != NULL) {
        int len = split(line, " ", result);
        if (len < 7) continue;

        // Total RO  Size (Code + RO Data)
        if (strcmp("Total", result[0]) == 0 && strcmp("RO", result[1]) == 0) {
            long ro_size = strtol(result[7], &end, 10);
            if ((errno != 0 && ro_size == 0) || *end != '\0') {
                LOG_ERROR("Failed to parse rw size %s\n", result[7]);
                return ERROR_STR_TO_NUM;
            }
            ctx->ro_used_size = ro_size;
            continue;
        }

        // Total RW  Size (RW Data + ZI Data)
        if (strcmp("Total", result[0]) == 0 && strcmp("RW", result[1]) == 0) {
            long rw_size = strtol(result[8], &end, 10);
            if ((errno != 0 && rw_size == 0) || *end != '\0') {
                LOG_ERROR("Failed to parse rw size %s\n", result[8]);
                return ERROR_STR_TO_NUM;
            }
            ctx->rw_used_size = rw_size;
            continue;
        }

        // Total ROM Size (Code + RO Data + RW Data)
        if (strcmp("Total", result[0]) == 0 && strcmp("ROM", result[1]) == 0) {
            long rom_size = strtol(result[10], &end, 10);
            if ((errno != 0 && rom_size == 0) || *end != '\0') {
                LOG_ERROR("Failed to parse rw size %s\n", result[10]);
                return ERROR_STR_TO_NUM;
            }
            ctx->rom_used_size = rom_size;
            continue;
        }
    }

    fclose(fp);

    return 0;
}

int parse_scatter_file(struct context *ctx) {
    FILE *fp;
    char line[120];

    fp = fopen(ctx->scatter_file, "r");
    if (fp == NULL) {
        LOG_ERROR("Failed to open file %s\n", ctx->scatter_file);
        return ERROR_FILE_NOT_FOUND;
    }

    char *end;
    errno = 0;
    char *result[15];
    while (fgets(line, sizeof(line), fp) != NULL) {
        if (start_with(line, ";")) continue;// comment
        int len = split(line, " ", result);
        if (len < 4) continue;                         // not include rom or ram size

        if (start_with(result[0], "LR_IROM")) {
            long rom_size = strtol(result[2], &end, 16);
            if ((errno != 0 && rom_size == 0) || *end != '\0') {
                LOG_ERROR("Failed to parse rw size %s\n", result[2]);
                return ERROR_STR_TO_NUM;
            }
            ctx->rom_size = rom_size;
            continue;
        }

        if (start_with(result[0], "RW_IRAM")) {
            long ram_size = strtol(result[2], &end, 16);
            if ((errno != 0 && ram_size == 0) || *end != '\0') {
                LOG_ERROR("Failed to parse rw size %s\n", result[2]);
                return ERROR_STR_TO_NUM;
            }
            ctx->rw_size += ram_size;
            continue;
        }
    }

    fclose(fp);

    return 0;
}


int main(int argc, char **argv) {
    struct context ctx;

    context_init(&ctx);

    parse_cmdline_args(&ctx, argc, argv);

    // get flash/ram used size through parsing the Keil .map file
    int ret = parse_map_file(&ctx);
    if (ret != ERROR_OK) {
        exit(ret);
    }

    LOG_INFO("RO Used Size:%d, RW Used Size:%d, ROM Used Size:%d\n", ctx.ro_used_size,
             ctx.rw_used_size, ctx.rom_used_size);

    // get flash/ram max size through parsing the Keil .sct file
    ret = parse_scatter_file(&ctx);
    if (ret != ERROR_OK) {
        exit(ret);
    }
    LOG_INFO("RAM Size:%X, ROM Size:%X\n", ctx.rw_size, ctx.rom_size);

    ctx.format(&ctx);

}
